﻿// This software is the confidential and proprietary information of NetBay.
// Such confidential information should not be disclosed.
// NetBay copyright(c) 2012

#region

using System;
using System.Collections.Generic;
using System.Linq;
using NetBay.Core.BusinessEntities.Workflow;
using NetBay.Core.BusinessEntities.Workflow.Rules;
using Rio.Flow.Business.Repository;
using Action = NetBay.Core.BusinessEntities.Workflow.Rules.Action;

#endregion

namespace Rio.Workflow.Runtime.Extentions
{
    public static class RulesExtention
    {
        #region Class Methods

        /// <summary>
        /// Determines whether this instance [can evaluate condition] the specified action.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <param name="wkfContext">The WKF contex.</param>
        /// <returns>
        ///   <c>true</c> if this instance [can evaluate condition] the specified action; otherwise, <c>false</c>.
        /// </returns>
        public static bool CanEvaluateCondition(Action action, WorkflowContextBase wkfContext)
        {
            List<WorkflowInstanceUser> instanceUsers =
                WorkflowInstanceRepository.GetUserInstances(wkfContext.CurrentInstance.Identifier);
            bool result = false;
            foreach (var userInstance in instanceUsers)
            {
                Dictionary<long, long> documents =
                    WorkflowInstanceRepository.GetDocumentStateByUserInstance(userInstance.Identifier,
                                                                              wkfContext.CurrentInstance.Identifier,
                                                                              userInstance.UserGroupId);

                if (documents.Count > 0)
                {
                    result = documents.All(p => p.Value == Convert.ToInt64(action.ValidatingState));
                }
                else
                {
                    result = false;
                }

                if (!result)
                    return false;
            }
            return result;
        }

        /// <summary>
        /// Gets the current action.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <returns></returns>
        public static Action GetCurrentAction(this Rules rules, WorkflowContextBase wkfContext)
        {
            var state = GetCurrentWkfState(rules, wkfContext);
            if (state != null)
            {
                return
                    state.Actions.Where(p => p.Identifier == wkfContext.CurrentAction.ActionOrConditionId).
                        FirstOrDefault();
            }
            return null;
        }

        /// <summary>
        /// Gets the state of the current WKF.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <returns></returns>
        public static WkfState GetCurrentWkfState(this Rules rules, WorkflowContextBase wkfContext)
        {
            return rules.WkfStates.Where(p => p.Value == wkfContext.OriginalInstance.State).FirstOrDefault();
        }

        /// <summary>
        /// Gets the state of the document.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <returns></returns>
        public static WorkflowState GetNewDocumentState(this Rules rules, WorkflowContextBase wkfContext)
        {
            var condition = GetCondition(rules, wkfContext, Target.Document);
            if (condition != null)
            {
                if (condition.If != null)
                {
                    bool result = ExecuteIf(condition.If,
                                            wkfContext.CurrentInstance.WorkItems.OfType<WorkflowDocument>().ToList());
                    if (result)
                        return condition.NewState;
                    return WorkflowState.None;
                }
                return condition.NewState;
            }
            return WorkflowState.None;
        }

        /// <summary>
        /// Gets the new state of the workflow.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <returns></returns>
        public static WorkflowState GetNewWorkflowState(this Rules rules, WorkflowContextBase wkfContext)
        {
            var condition = GetConditionWithValidatingState(rules, wkfContext, Target.Workflow);
            if (condition != null)
            {
                if (condition.If != null)
                {
                    //Executes if condition
                    bool result = ExecuteIf(condition.If,
                                            wkfContext.CurrentInstance.WorkItems.OfType<WorkflowDocument>().ToList());
                    if (result)
                    {
                        return condition.NewState;
                    }

                    //Check if all documents have a state to apply the new state
                    result =
                        wkfContext.CurrentInstance.WorkItems.OfType<WorkflowDocument>().All(
                            p => p.DocumentStatus != WorkflowState.None);
                    if (result)
                    {
                        return condition.NewState;
                    }
                }
                else
                {
                    return condition.NewState;
                }
            }

            //In all others case we return no new state
            return WorkflowState.None;
        }

        /// <summary>
        /// Workflows the final state of the is.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <returns></returns>
        public static bool WorkflowIsFinalState(this Rules rules, WorkflowContextBase wkfContext)
        {
            ////////////////////// Here we get that /////////////////////////////////////////////////
            //<Conditions>
            //  <Condition Target="Document" IsFinalState="false" StateMode="One" NewState="Indexed" />
            //  <Condition Target="Workflow" IsFinalState="true" NewState="Indexed">
            //    <If Operator="Or">
            //      <States>
            //        <State Target="Document" StateMode="All" Value="Indexed" />
            //        <State Target="Document" StateMode="All" Value="Rejected" />
            //      </States>
            //    </If>
            //  </Condition>
            //</Conditions>
            //////////////////////////////////////////////////////////////////////////////////////////

            bool result = false;
            Condition condition = GetConditionWithValidatingState(rules, wkfContext, Target.Workflow);
            if (condition != null)
            {
                if (condition.If != null)
                {
                    result = ExecuteIf(condition.If,
                                       wkfContext.OriginalInstance.WorkItems.OfType<WorkflowDocument>().ToList());
                }
                else
                {
                    //With no If condition
                    result = condition.IsFinalState;
                }
            }
            return result;
        }

        /// <summary>
        /// Executes the condition.
        /// </summary>
        /// <param name="mode">The mode.</param>
        /// <param name="documents">The documents.</param>
        /// <param name="state">The state.</param>
        /// <returns></returns>
        private static bool ExecuteCondition(SelectionMode mode, List<WorkflowDocument> documents, WorkflowState state)
        {
            switch (mode)
            {
                case SelectionMode.All:
                    return documents.All(p => p.DocumentStatus == state);
                case SelectionMode.One:

                    var doc = documents.Count(p => p.DocumentStatus == state);
                    if (doc == 1)
                    {
                        return true;
                    }
                    return false;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Executes if.
        /// </summary>
        /// <param name="ifClause">If clause.</param>
        /// <param name="documents">The documents.</param>
        /// <returns></returns>
        private static bool ExecuteIf(If ifClause, List<WorkflowDocument> documents)
        {
            if (ifClause == null)
                throw new ArgumentNullException("ifClause");


            bool result = false;
            foreach (var item in ifClause.States)
            {
                //ActionExecuted the condition 
                bool newvalue = ExecuteCondition(item.StateMode, documents, item.Value);

                //Then execute the operator condition
                result = ExecuteOperator(ifClause.Operator, result, newvalue);
            }
            return result;
        }

        /// <summary>
        /// Executes the operator.
        /// </summary>
        /// <param name="op">The op.</param>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        private static bool ExecuteOperator(Operator op, bool oldValue, bool newValue)
        {
            bool result = false;
            switch (op)
            {
                case Operator.Or:
                    result = (oldValue | newValue);
                    break;

                case Operator.And:
                    result = (oldValue & newValue);
                    break;

                default:
                    result = false;
                    break;
            }
            return result;
        }

        /// <summary>
        /// Gets the condition.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <param name="target">The target.</param>
        /// <returns></returns>
        private static Condition GetCondition(this Rules rules, WorkflowContextBase wkfContext, Target target)
        {
            var action = GetCurrentAction(rules, wkfContext);
            if (action != null)
            {
                return action.Conditions.Where(p => p.Target == target).FirstOrDefault();
            }

            return null;
        }

        /// <summary>
        /// Gets the condition.
        /// </summary>
        /// <param name="rules">The rules.</param>
        /// <param name="wkfContext">The WKF context.</param>
        /// <param name="target">The target.</param>
        /// <returns></returns>
        private static Condition GetConditionWithValidatingState(this Rules rules, WorkflowContextBase wkfContext,
                                                                 Target target)
        {
            bool canEvaluate = false;
            var action = GetCurrentAction(rules, wkfContext);
            if (action != null)
            {
                //Evaluate conditions only if 
                switch (action.ValidatingMode)
                {
                    case SelectionMode.One:
                        canEvaluate = true;
                        break;
                    case SelectionMode.All:
                        canEvaluate = CanEvaluateCondition(action, wkfContext); //true
                        break;
                }
            }

            if (canEvaluate)
            {
                return action.Conditions.Where(p => p.Target == target).FirstOrDefault();
            }
            return null;
        }

        #endregion
    }
}